// DTM.cpp
//
#include "DTM.h"

#include "RasterEngine.h"
#include "GlobalResources.h"
#include "TerrainShading.h"
//#include "ColorRamp.h"

#include "DTMManager/DTMVisualization.h"
#include "DTMManager/DTMManager.h"
#include "DTMManager/DTMQuadTreeNode.h"
#include "DTMManager/DTMManagerThread.h"

#include "TimedBlock.h"
#include "MemCheck.h"

using namespace IDS;
using namespace IDS::OpenGL;

int DTMNode::nextID = 0;

// ------------------------------------------------------------------------------------------------
void DTMNode::build(const DTMManager::SnapShot2D &s, double size)
{
	nodeSize = size;
	build(s);
}

// ------------------------------------------------------------------------------------------------
void DTMNode::build(const DTMManager::SnapShot2D &s)
{
	DTMManager::RasterData *raster = (DTMManager::RasterData*) s.m_raster.get();

	minValue = raster->m_min;
	maxValue = raster->m_max;

	nodeDL = glGenLists(1);
	

	const int w = raster->m_col;
	const int h = raster->m_row;
	texture.create(0,GL_LUMINANCE16,GL_LUMINANCE,GL_UNSIGNED_SHORT,false,w,h, raster->m_data );
	texture.setParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	texture.setParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	//texture.setParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	//texture.setParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	
	texture.setParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	texture.setParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	/* build normal map*/
	normalMap.create(0,4,GL_RGBA,GL_UNSIGNED_BYTE,false,w,h,NULL);
	normalMap.setParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	normalMap.setParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	//normalMap.setParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	//normalMap.setParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	normalMap.setParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	normalMap.setParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	fbo.create(w,h);
	fbo.addAttachment(GL_TEXTURE_2D, GL_RGBA, normalMap.getID() );
	GLenum att[] = {GL_COLOR_ATTACHMENT0};
	fbo.setDrawBuffers(1, att );

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT );
	glDepthMask(GL_TRUE);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);
	
	glColor4f(1,1,1,1);
	//glDrawBuffer(GL_NONE);
	//IDEAS::GlobalResources::terrainShader.attachSampler( normalMap.getID() );
	fbo.bind();
	fbo.clear();
	IDEAS::GlobalResources::terrainShader.bindSampler( minValue, maxValue, nodeSize );
	texture.bind();
	glBegin(GL_TRIANGLE_STRIP); //inv N
		glMultiTexCoord2f(GL_TEXTURE0,0.f,1.f); glVertex3f(-1.f, 1.f,-1.f);
		glMultiTexCoord2f(GL_TEXTURE0,0.f,0.f); glVertex3f(-1.f,-1.f,-1.f);
		glMultiTexCoord2f(GL_TEXTURE0,1.f,1.f); glVertex3f( 1.f, 1.f,-1.f);
		glMultiTexCoord2f(GL_TEXTURE0,1.f,0.f); glVertex3f( 1.f,-1.f,-1.f);
	glEnd();
	texture.unBind();
	//glDrawBuffer(GL_BACK);

	glPopMatrix();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	IDEAS::GlobalResources::terrainShader.unBindSampler();
	fbo.unBind();
	fbo.destroy(false);
	glPopAttrib();
	/**/	
	
	
	glNewList(nodeDL,GL_COMPILE);

	TerrainEngineUtility::Mesh2D * mesh = dynamic_cast<TerrainEngineUtility::Mesh2D*>(s.m_mesh.get());
	
	glBegin(GL_TRIANGLES);
	for(size_t i = 0; i<mesh->m_trig.size(); ++i)
	{	
		const TerrainEngineUtility::Vertex2D vert = mesh->m_vert[mesh->m_trig[i]];

		glMultiTexCoord2dv( GL_TEXTURE0, vert.m_uvPoint.v );
		glVertex3d(vert.m_xyPoint[0],vert.m_xyPoint[1],vert.m_deltaVert);
	}
	glEnd();
	glEndList();
}

// ------------------------------------------------------------------------------------------------
void DTMNode::draw()
{
	glActiveTexture(GL_TEXTURE2);
	normalMap.bind();
	glActiveTexture(GL_TEXTURE0);

	IDEAS::GlobalResources::terrainShader.setCurrentMinMax(minValue,maxValue);
	RasterNode::draw();

	glActiveTexture(GL_TEXTURE2);
	normalMap.unBind();
	glActiveTexture(GL_TEXTURE0);
}

// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DTM::~DTM()
{
	r_deleteTree(root);
}

// ------------------------------------------------------------------------------------------------
std::string DTM::getFilename()
{
	return raster->getFileName();
}

// ------------------------------------------------------------------------------------------------
void DTM::r_deleteTree(DTMNode *node)
{
	if(!node)
		return;

	for(int i=0;i<node->ariety(); ++i)
		r_deleteTree( (DTMNode *)node->getChild(i) );

	delete node;
}

// ------------------------------------------------------------------------------------------------
// no recoursion on r_buildTree
DTMNode *DTM::r_buildTree(int &counter,int lvl)
{
#if 0
	// ask for a valid id
	int nodeID = raster->getNodeID(counter++);
	if( nodeID < 0 )
		return NULL;

	// create the node
	DTMNode *node = new DTMNode( counter++, nodeID );

	IDSBox bb;
	raster->getNodeBoundingBox(nodeID,bb);

	node->setBoundingBox(bb);

	for(int i=0; i<DTMNode::ariety(); ++i)
		node->setChild( r_buildTree(counter,lvl+1), i);

	return node;
#else
	return buildResourcesTree(NULL,counter,-1);
#endif	
}

// ------------------------------------------------------------------------------------------------
DTMNode *DTM::buildResourcesTree(DTMNode *parent, int &counter, int children)
{
	if(!parent)
	{
		unsigned int resID  = raster->getRootID();
		DTMNode *n   = new DTMNode(DTMNode::nextID++,resID);

		IDSBox bb;
		raster->getNodeBoundingBox(resID,bb);

		n->setBoundingBox( bb );

		//for(int i=0; i<DTMNode::ariety(); ++i)
		//	n->setChild( buildResourcesTree(n,counter,i), i);

		return n;
	}

	//std::vector<int> vecID = raster->getChildrenID( parent->getResourceID() );

	//if( vecID.size() == 0)
	//	return NULL;

	//unsigned int resID = vecID[children];
	//DTMNode *n = new DTMNode(counter++,resID);
	//
	//IDSBox bb;
	//raster->getNodeBoundingBox(resID,bb);

	//n->setBoundingBox( bb );

	//for(int i=0; i<DTMNode::ariety(); ++i)
	//	n->setChild( buildResourcesTree(n,counter,i), i);

	//return n;

	return NULL;
}


// ------------------------------------------------------------------------------------------------
bool DTM::isLoading()
{		return true;
	//const int  n = DTMManager::g_getLoadableThread();
	//return loading || n > 0;
}

// ------------------------------------------------------------------------------------------------
//bool DTM::getHeight(const IDSReal3D &p, double &h)
//{
//	double tmp;
//	//if( raster->getDTMInterface()->getElevation(p[0],p[1],tmp) == DTMManager::DTMMANAGER_NO_ERROR )
//	if( raster->getDTMInterface()->getElevation(p,tmp) == DTMManager::DTMMANAGER_NO_ERROR )
//	{
//		h = tmp;
//		return true;
//	}
//
//	return false;
//}

// ------------------------------------------------------------------------------------------------
void DTM::flushUnusedNodes() 
{
	std::vector<DTMNode*> entries = cachedNodes.getEntries();

	for(size_t i=0; i<entries.size(); ++i)
	{
		if( !entries[i]->isLocked() &&  !entries[i]->isRoot() )
		{
#ifdef _DEBUG
			printf("garbaged %d\n",entries[i]->getID() );
#endif
			cachedNodes.remove( entries[i]->getID() );
			entries[i]->reset();
		}
	}
}

// ------------------------------------------------------------------------------------------------
bool DTM::r_lockNode(DTMNode *node)
{
	if(!node)	return false;

	//Frustum intersection test
	if( !currViewBBox.overlaps2D(node->getBoundingBox()) )
	{
		node->setFrustumIntersection(DTMNode::OUTSIDE);
		node->unlock();
		return false;
	}
	node->setFrustumIntersection(DTMNode::INSIDE);


	// Compute screen space error
	const double nSize = node->getBoundingBox().size().mod();

	// ***************************************************
	// comment this block to prevent always-loading of root node
	if( node->isRoot() )
	{
		if( !node->isValid() )
		{
			DTMManager::SnapShot2D snap;
			
			updateRequest( node->getID() );
			int result = raster->loadNode(node->getResourceID(),snap,TerrainEngineUtility::EnginePriority::NonUrgent);
			if( result == TerrainEngineUtility::TERRAINENGINEUTILITY_NO_ERROR)
			{
				root->build(snap,nSize);
				raster->clearNode( node->getResourceID() );
			}
		}
#ifdef _DEBUG
		requestQueue.clear();
#endif
	}
	// ***************************************************

	
	bool lockable = true;

	int locked = 0;
	int inside = 0;
	int validChildren = 0;

	// if current size is < threshold this node needs to be refined
	if( viewSize / nSize < threshold /*&& node->getLevel() < 4*/)
	{
		
		for(int i=0; i<node->ariety(); ++i)
			if( node->getChild(i) )
				validChildren++;

		// not all children have been created yet
		if( validChildren < node->ariety() )
		{
			validChildren = 0;
			std::vector<int> vecID = raster->getChildrenID( node->getResourceID() );
			
			for(size_t i=0; i<vecID.size(); ++i)
			{
				//lastLevel = false;
				unsigned int resID = vecID[i];
	
				IDSBox bb;
				raster->getNodeBoundingBox(resID,bb);

				if( !currViewBBox.overlaps2D(bb) )
				{
					node->setFrustumIntersection(DTMNode::PARTIAL);
					continue;
				}
				
				
				if( !node->getChild(i) )
				{
					DTMNode *n = new DTMNode(DTMNode::nextID++,resID);
					n->setBoundingBox( bb );
					n->setFrustumIntersection(DTMNode::INSIDE);

					node->setChild( n, i);
				}
				validChildren++;
			}
		}

		if( validChildren>0 )
		{
			for(size_t i=0; i<node->ariety(); ++i)
			{
				DTMNode *ch = (DTMNode *)node->getChild(i);

				if(	r_lockNode( ch ) )	locked ++;
				if( ch && ch->getFrustumIntersection() != DTMNode::OUTSIDE) 
					inside++;
			}

			// ---------------------------------------------------------
			// count locked children
			if( locked == inside )
			{
				lockedNodes.remove(node);
			
				node->unlock();
				lockable = false;
			}
			// ---------------------------------------------------------
		}
	}

	if( inside < node->ariety() )
		node->setFrustumIntersection(DTMNode::PARTIAL);

	if( lockable && !node->isValid() )
	{
		DTMManager::SnapShot2D snap;
		TerrainEngineUtility::EnginePriority::Priority priority = TerrainEngineUtility::EnginePriority::Normal;
		
		if( highPriorityBBox.overlaps2D( node->getBoundingBox() ) )
			priority = TerrainEngineUtility::EnginePriority::Urgent;

		updateRequest( node->getID() );


		int result = raster->loadNode(node->getResourceID(),snap,priority);

		if(	result == TerrainEngineUtility::TERRAINENGINEUTILITY_NO_ERROR)
		{
			// build a new mesh with texture
			node->build(snap,nSize);
				
			// free raster data
			raster->clearNode( node->getResourceID() );
				
			// cache the new mesh 
			DTMNode *lru = cachedNodes.insert( node->getID(), node);
			if( lru )
			{
				// lru has been cached out because too old
				// free its memory
#ifdef _DEBUG
				printf("Removing LRU node %d\n",node->getResourceID() );
#endif
				lru->reset();
			}
		}
#ifdef _DEBUG
		else
			requestQueue.push_back(node->getResourceID());
#endif
	}

	const bool v = node->isValid();
	// insert the note if is valid and not root
	if( lockable && v  )
	{
		node->lock();
		lockedNodes.push_back(node);
	}

	if(node->isRoot() && v && lockedNodes.size() == 0)
	{
		node->lock();
		lockedNodes.push_back(node);
		return true;
	}

	return lockable ? (lockable && v) : (locked == inside);
}

// ------------------------------------------------------------------------------------------------
void DTM::draw()
{
	//if(lockedNodes.size() > 0)
	//	printf("*******************************\n");
	glColor4f(1.f,1.f,1.f,opacity);

	for(std::list<OpenGL::DTMNode*>::reverse_iterator jt = lockedNodes.rbegin(); jt!=lockedNodes.rend(); ++jt)
	{
		cachedNodes.use((*jt)->getID());
		(*jt)->draw();
		(*jt)->unlock();
	}
	
	//if(lockedNodes.size() > 0)
	//	printf("\n");
}